// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.ComponentModel;
using System.Configuration;
#if DEBUG
using System.Diagnostics;
#endif
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Xml.Schema;
using System.Xml.Serialization.Configuration;

namespace System.Xml.Serialization
{
    public abstract class SchemaImporter
    {
        private XmlSchemas _schemas;
        private StructMapping? _root;
        private readonly CodeGenerationOptions _options;
        private TypeScope? _scope;
        private ImportContext _context;
        private bool _rootImported;
        private NameTable? _typesInUse;
        private NameTable? _groupsInUse;

        [RequiresUnreferencedCode("calls SetCache")]
        internal SchemaImporter(XmlSchemas schemas, CodeGenerationOptions options, ImportContext context)
        {
            if (!schemas.Contains(XmlSchema.Namespace))
            {
                schemas.AddReference(XmlSchemas.XsdSchema);
                schemas.SchemaSet.Add(XmlSchemas.XsdSchema);
            }
            if (!schemas.Contains(XmlReservedNs.NsXml))
            {
                schemas.AddReference(XmlSchemas.XmlSchema);
                schemas.SchemaSet.Add(XmlSchemas.XmlSchema);
            }
            _schemas = schemas;
            _options = options;
            _context = context;
            Schemas.SetCache(Context.Cache, Context.ShareTypes);
        }

        internal ImportContext Context => _context ??= new ImportContext();

        internal Hashtable ImportedElements
        {
            get { return Context.Elements; }
        }

        internal Hashtable ImportedMappings
        {
            get { return Context.Mappings; }
        }

        internal CodeIdentifiers TypeIdentifiers
        {
            get { return Context.TypeIdentifiers; }
        }

        internal XmlSchemas Schemas => _schemas ??= new XmlSchemas();

        internal TypeScope Scope => _scope ??= new TypeScope();

        internal NameTable GroupsInUse => _groupsInUse ??= new NameTable();

        internal NameTable TypesInUse => _typesInUse ??= new NameTable();

        internal CodeGenerationOptions Options
        {
            get { return _options; }
        }

        [RequiresUnreferencedCode("calls GetTypeDesc")]
        internal void MakeDerived(StructMapping structMapping, Type? baseType, bool baseTypeCanBeIndirect)
        {
            structMapping.ReferencedByTopLevelElement = true;
            TypeDesc baseTypeDesc;
            if (baseType != null)
            {
                baseTypeDesc = Scope.GetTypeDesc(baseType);
                if (baseTypeDesc != null)
                {
                    TypeDesc typeDescToChange = structMapping.TypeDesc!;
                    if (baseTypeCanBeIndirect)
                    {
                        // if baseTypeCanBeIndirect is true, we apply the supplied baseType to the top of the
                        // inheritance chain, not necessarily directly to the imported type.
                        while (typeDescToChange.BaseTypeDesc != null && typeDescToChange.BaseTypeDesc != baseTypeDesc)
                            typeDescToChange = typeDescToChange.BaseTypeDesc;
                    }
                    if (typeDescToChange.BaseTypeDesc != null && typeDescToChange.BaseTypeDesc != baseTypeDesc)
                        throw new InvalidOperationException(SR.Format(SR.XmlInvalidBaseType, structMapping.TypeDesc!.FullName, baseType.FullName, typeDescToChange.BaseTypeDesc.FullName));
                    typeDescToChange.BaseTypeDesc = baseTypeDesc;
                }
            }
        }

        internal string GenerateUniqueTypeName(string typeName)
        {
            typeName = CodeIdentifier.MakeValid(typeName);
            return TypeIdentifiers.AddUnique(typeName, typeName);
        }

        [RequiresUnreferencedCode("calls GetTypeDesc")]
        private StructMapping CreateRootMapping()
        {
            TypeDesc typeDesc = Scope.GetTypeDesc(typeof(object));
            StructMapping mapping = new StructMapping();
            mapping.TypeDesc = typeDesc;
            mapping.Members = Array.Empty<MemberMapping>();
            mapping.IncludeInSchema = false;
            mapping.TypeName = Soap.UrType;
            mapping.Namespace = XmlSchema.Namespace;

            return mapping;
        }

        [RequiresUnreferencedCode("calls CreateRootMapping")]
        internal StructMapping GetRootMapping() => _root ??= CreateRootMapping();

        [RequiresUnreferencedCode("calls GetRootMapping")]
        internal StructMapping ImportRootMapping()
        {
            if (!_rootImported)
            {
                _rootImported = true;
                ImportDerivedTypes(XmlQualifiedName.Empty);
            }
            return GetRootMapping();
        }

        [RequiresUnreferencedCode("calls ImportType")]
        internal abstract void ImportDerivedTypes(XmlQualifiedName baseName);

        internal static void AddReference(XmlQualifiedName name, NameTable references, string error)
        {
            if (name.Namespace == XmlSchema.Namespace)
                return;
            if (references[name] != null)
            {
                throw new InvalidOperationException(string.Format(error, name.Name, name.Namespace));
            }
            references[name] = name;
        }

        internal static void RemoveReference(XmlQualifiedName name, NameTable references)
        {
            references[name] = null;
        }

        internal void AddReservedIdentifiersForDataBinding(CodeIdentifiers scope)
        {
            if ((_options & CodeGenerationOptions.EnableDataBinding) != 0)
            {
                scope.AddReserved("PropertyChanged");
                scope.AddReserved("RaisePropertyChanged");
            }
        }
    }
}
